package com.christianposta.learnddd.domain.model.cargo;

import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.Validate;

import com.christianposta.learnddd.domain.model.handling.HandlingEvent;
import com.christianposta.learnddd.domain.model.handling.HandlingType;
import com.christianposta.learnddd.domain.model.location.Location;
import com.christianposta.learnddd.domain.shared.ValueObject;

public class Itinerary implements ValueObject<Itinerary>{
	
	static final Itinerary EMPTY_ITINERARY = new Itinerary();
	
	private static final Date END_OF_DAYS = new Date(Long.MAX_VALUE);
	
	private List<Leg> legs = Collections.emptyList();
	private static final Date NO_DATE = new Date();
	
	public Itinerary(final List<Leg> legs){
		Validate.notEmpty(legs);
		Validate.noNullElements(legs);
		
		this.legs = legs;
	}
	
	public List<Leg> legs(){
		return Collections.unmodifiableList(this.legs);
	}
	
	/**
	 * Based on the possible legs of the itinerary, determine whether
	 * a particular handling event could be expected. For example, if one
	 * of the legs involves a stop in chicago, a UNLOAD handling event in chicago
	 * could possibly happen, or is to be expected, and the method would
	 * return true.
	 * 
	 * @param event
	 * @return
	 */
	public boolean isExpected(final HandlingEvent event){
		
		// if there are no legs associated 
		if(this.legs.isEmpty()){
			return true;
		}
		
		if(event.type() == HandlingType.RECEIVE){
			// Check that the first leg's origin is the event's location
			Leg leg = firstLeg();
			return leg.loadLocation().sameIdentityAs(event.location());
		}
		
		if(event.type() == HandlingType.LOAD){
			// Check that there is one leg with the same load location and voyage
			for(Leg leg : this.legs){
				if(leg.loadLocation().sameIdentityAs(event.location()) && 
						leg.voyage().sameIdentityAs(event.voyage())){
					return true;
				}
			}
			return false;
		}
		
		if(event.type() == HandlingType.UNLOAD){
			// Check that there is one leg with the same unload location and voyage
			for(Leg leg : this.legs){
				if(leg.unloadLocation().sameIdentityAs(event.location()) && 
						leg.voyage().sameIdentityAs(event.voyage())){
					return true;
				}
			}
			return false;
		}
		
		if(event.type() == HandlingType.CLAIM){
			return lastLeg().unloadLocation().sameIdentityAs(event.location());
		}
		
		return true;
	}
	
	
	/**
	 * The initial departure for this itinerary. 
	 * 
	 * This method is marked package-private because it's only
	 * to be used inside the Cargo aggregate. 
	 * @return
	 */
	Location initialDepartureLocation(){
		if(this.legs.isEmpty()){
			return Location.UNKNOWN;
		}
		Leg firstLeg = firstLeg();
		return firstLeg.loadLocation();
	}
	
	/**
	 * The final arrival location of the cargo for this itinerary
	 * 
	 * @return
	 */
	Location finalArrivalLocation() {

		if (legs.isEmpty()) {
			return Location.UNKNOWN;
		}

		Leg lastLeg = lastLeg();
		return lastLeg.unloadLocation();
	}
	
	Leg lastLeg(){
		if(this.legs.isEmpty()){
			return null;
		}
		
		int lastLegIndex = this.legs.size() - 1;
		return this.legs.get(lastLegIndex);
	}
	
	Leg firstLeg(){
		if(this.legs.isEmpty()){
			return null;
		}
		
		return this.legs.get(0);
	}
	
	Date finalArrivalDate(){
		final Leg lastLeg = lastLeg();
		
		if(lastLeg == null){
			return new Date(END_OF_DAYS.getTime());
		}
		else {
			return new Date(lastLeg.unloadDate().getTime());
		}
	}
	
	
	/**
	 * For the itinerary value object to have the same value as any
	 * other itinerary value object, they must both have the exact same
	 * legs list.
	 */
	public boolean sameValueAs(Itinerary other) {
		return other != null && this.legs.equals(other.legs);
	}

	Itinerary() {
		// Needed by Hibernate
	}
}
